unit Registration; {************************************************************************} {* Registration.p *} {* by Michael Castle *} {* University of Michigan Mental Health Research Institute (MHRI) *} {* e-mail address: mike.castle@med.umich.edu *} {* last modified on 11 March 1994 *} {************************************************************************} interface uses QuickDraw, PrintTraps, Palettes, Globals, Utilities, File2, File1, Graphics, Camera, Text, Filters, Stacks; procedure DoRegister; implementation const RegisterImagesID = 129; {Dialog IDs} FiducialsOnScreenID = 3; FiducialsFromFileID = 4; ConfirmFidClicksID = 5; biggestFid = 9999; {maximum allowable fiducial stage coordinate} MaxFids = 12; {maximum number of fiducial points per slice} MaxRegSlices = 250; type RegisterRealArray = array[1..MaxRegSlices] of real; RealPoint = record x: real; y: real; end; {record} FidArray = array[1..MaxRegSlices, 1..MaxFids] of RealPoint; {******************************************************************************} {* RotateAboutXY rotates the point (x,y) counterclockwise by 'angle' radians about the point (xcenter, *} {* ycenter). *} {******************************************************************************} procedure RotateAboutXY (var x, y: real; angle: real; xcenter, ycenter: real); var x0, y0: real; SinAngle, CosAngle: real; begin x0 := x; y0 := y; CosAngle := cos(angle); SinAngle := sin(angle); x := (x0 - xcenter) * CosAngle - (y0 - ycenter) * SinAngle + xcenter; y := (y0 - ycenter) * CosAngle + (x0 - xcenter) * SinAngle + ycenter; end; {RotateAboutXY} {******************************************************************************} {* Read from a file the fiducial data necessary to register a set of images. The data file contains several lines *} {* of coordinates delimited by tabs (x-coordinate followed by y-coordinate in all cases). The first line of the *} {* file should hold the coordinates of the Image Center point, the location (in screen coordinates) of the *} {* microscope crosshairs as they appear on the screen during image capture. The second line of the file should *} {* give the location in screen coordinates of two fixed points in an image (under the camera and microscope *} {* conditions selected for capture of the set of images to be aligned). The third line of the file should provide *} {* the location of these two fixed points in microscope stage coordinates. Each subsequent line in the file *} {* should contain (in stage coordinates) the locations of the Image Center and at least two fiducial points for an *} {* image in the set to be registered. Each image must be represented by the same number of fiducial points in *} {* the data file. Where fiducial coordinates are unavailable, coordinates of biggestFid+1 should appear. No *} {* more than MaxFids fiducial points are allowed for each image. *} {******************************************************************************} function GetFiducialDataFromFile (var xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2: integer; var xstage1, ystage1, xstage2, ystage2: real; var fiducials: FidArray; var xc, yc: RegisterRealArray): boolean; var fiducialfname, str: str255; RefNum, nValues, i, j, nImages: integer; rLine: RealLine; begin nImages := info^.StackInfo^.nSlices; GetFiducialDataFromFile := FALSE; ShowMessage('Please open a fiducial data file.'); if not GetTextFile(fiducialfname, RefNum) then exit(GetFiducialDataFromFile); InitTextInput(fiducialfname, RefNum); GetLineFromText(rLine, nValues); if (nValues <> 2) then begin PutMessage('Expecting screen coordinates of Image Center point in line 1. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xcscreen := round(rLine[1]); ycscreen := round(rLine[2]); GetLineFromText(rLine, nValues); if (nValues <> 4) then begin PutMessage('Expecting screen coordinates of two points in line 2. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xscreen1 := round(rLine[1]); yscreen1 := round(rLine[2]); xscreen2 := round(rLine[3]); yscreen2 := round(rLine[4]); GetLineFromText(rLine, nValues); if (nValues <> 4) then begin PutMessage('Expecting stage coordinates of two points in line 3. Please edit fiducial data file and try again.'); exit(GetFiducialDataFromFile); end; xstage1 := rLine[1]; ystage1 := rLine[2]; xstage2 := rLine[3]; ystage2 := rLine[4]; i := 1; GetLineFromText(rLine, nValues); while (nvalues > 0) do begin if nValues >= 6 then begin for j := 1 to (nvalues - 2) div 2 do begin fiducials[i, j].x := rLine[j * 2 + 1]; fiducials[i, j].y := rLine[j * 2 + 2]; end; for j := (nvalues - 2) div 2 + 1 to MaxFids do begin fiducials[i, j].x := biggestFid + 1; fiducials[i, j].y := biggestFid + 1; end; {for j} xc[i] := rLine[1]; yc[i] := rLine[2]; end else begin str := StringOf('Expecting coordinates of image center and at least two fiducial points in line ', (i + 3) : 1, '. Please edit fiducial data file and try again.'); PutMessage(str); exit(GetFiducialDataFromFile); end; i := i + 1; GetLineFromText(rLine, nValues); end; {while} if (i < (nImages + 1)) then begin if (i = nImages) then str := StringOf('Expecting fiducial data for one more image. Please edit fiducial data file, then try again. ') else str := StringOf('Expecting fiducial data for ', (nImages + 1 - i) : 1, ' more slices. Please edit fiducial data file, then try again. '); PutMessage(str); exit(GetFiducialDataFromFile); end; if (i > (nImages + 1)) then begin if (i = nImages + 2) then str := StringOf('Too much fiducial data. Please edit fiducial data file, then try again. ') else str := StringOf('Too much fiducial data. Please edit fiducial data file, then try again. '); PutMessage(str); exit(GetFiducialDataFromFile); end; GetFiducialDataFromFile := TRUE; end; {GetFiducialDataFromFile} {******************************************************************************} {* Read the coordinates of a fiducial point entered on the screen by clicking once with the mouse. Interpret *} {* a double-click to indicate that this is the last fiducial point for the current slice, a spacebar-click to *} {* to indicate that no valid fiducial exists corresponding to this fiducial in other slices (record BiggestFid+1 *} {* as value for each coordinate), an option-click to indicate that some data for this slice has been improperly *} {* and the user would like to re-enter all fiducials for this slice, and command-period to indicate that the *} {* user wishes to cancel image registration altogether, discarding all entered fiducials coordinates. *} {******************************************************************************} function GetNextFiducial (var Fidx, Fidy: integer; var done, redo: boolean): boolean; var pt1, pt2: point; sbdown, optdown, DoubleClick: boolean; MouseUpTime: LongInt; begin SetCursor(ToolCursor[SelectionTool]); GetNextFiducial := FALSE; repeat sbdown := SpaceBarDown; optdown := OptionKeyDown; SetPort(info^.wptr); GetMouse(pt1); Show3Values(pt1.h, pt1.v, MyGetPixel(pt1.h, pt1.v)); if CommandPeriod then begin ShowMessage('Fiducial input cancelled.'); exit(GetNextFiducial); end; {then} until button; repeat until not (button); MouseUpTime := TickCount; DoubleClick := FALSE; repeat GetMouse(pt2); if EqualPt(pt1, pt2) then DoubleClick := button; until (TickCount - MouseUpTime > GetDblTime) or DoubleClick; if sbdown then begin Fidx := BiggestFid + 1; Fidy := BiggestFid + 1; end else if optdown then redo := TRUE else begin Fidx := pt1.h; Fidy := (Info^.nLines - 1) - pt1.v; end; done := DoubleClick; while (button) do {clear out any buffered mouse clicks; I don't know why there would be any} ; {such clicks, but they can be *very* disruptive while running on Quadras!} FlushEvents(62, 0); {make sure clicks and key presses don't linger in the event queue after exit} GetNextFiducial := TRUE; end; {GetNextFiducial} procedure SetSlice (i: integer); begin SelectSlice(i); Info^.StackInfo^.CurrentSlice := i; UpdateTitleBar; end; {******************************************************************************} {* Read fiducial coordinates for a set of slices to be placed in register with a reference slice (a member of *} {* the set). Begin by reading fiducials for the reference slice, then read the rest. Assign dummy values to *} {* variables in the fiducial data structure whose values would be used for mapping between two coordinate *} {* systems had fiducial data been read from a file. *} {******************************************************************************} function GetFiducialDataFromScreen (var xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2: integer; var xstage1, ystage1, xstage2, ystage2: real; var fiducials: FidArray; var xc, yc: RegisterRealArray): boolean; var i, j: integer; nImages: integer; {number of slices} RefSlice: integer; {index of the reference slice} Fidx, Fidy: integer; {coordinates of selected fiducial point} done: boolean; {done entering fiducials for this slice} redo: boolean; {re-enter fiducials for this slice} str: str255; begin GetFiducialDataFromScreen := FALSE; nImages := Info^.StackInfo^.nSlices; xcscreen := 100; {arbitrarily assign dummy image center} ycscreen := 100; xscreen1 := 50; {assign screen and stage coords of two points} yscreen1 := 50; {so that mapping is 1:1 in x and y} xscreen2 := 80; yscreen2 := 80; xstage1 := 50.0; ystage1 := 50.0; xstage2 := 80.0; ystage2 := 80.0; DrawLabels('X: ', 'Y: ', 'Value: '); {prepare to show x,y values in results window} RefSlice := info^.StackInfo^.CurrentSlice; i := RefSlice; {begin with reference slice} while (i <= nImages) do begin done := FALSE; redo := FALSE; SetSlice(i); UpdatePicWindow; for j := 1 to MaxFids do begin if (not done) and (not redo) then begin str := StringOf('Click on fiducial point ', j : 1); showmessage(str); if not GetNextFiducial(Fidx, Fidy, done, redo) then exit(GetFiducialDataFromScreen); if ConfirmFidClicks then begin str := StringOf('Fiducial point ', j : 1, ': x = ', Fidx : 1, ' y = ', Fidy : 1); if not (redo) then while (PutMessageWithCancel(str) = cancel) do begin UpdatePicWindow; if not GetNextFiducial(Fidx, Fidy, done, redo) then exit(GetFiducialDataFromScreen); str := StringOf('Fiducial point ', j : 1, ': x = ', Fidx : 1, ' y = ', Fidy : 1); end; {while} UpdatePicWindow; end; {then} if done and (j = 1) then begin PutMessage('You must select at least two fiducial points in each slice for registration.'); redo := TRUE; end; {then} fiducials[i, j].x := Fidx; fiducials[i, j].y := Fidy; end {then} else begin {pad rest of array with invalid fiducial data} fiducials[i, j].x := biggestFid + 1; fiducials[i, j].y := biggestFid + 1; end; {else} end; {for j} xc[i] := 100; yc[i] := 100; if not (redo) then begin if (i = RefSlice) and (RefSlice <> 1) then i := 1 else if (i = RefSlice - 1) then {don't read fiducials from reference slice twice!} i := i + 2 else i := i + 1; end {then} else PutMessage('Input cancelled. Please reselect fiducial points for this slice.'); end; {while} GetFiducialDataFromScreen := TRUE; end; {GetFiducialDataFromScreen} {******************************************************************************} {* Before rotating and translating an image into register, center it in a larger buffer so that rotation does *} {* not unnecessarily clip portions of the image that will return to view after translation. *} {******************************************************************************} procedure CenterInBigBuffer (i, picwidth, picheight, bigpicwidth, bigpicheight: integer; StackInfo: InfoPtr); var vloc, hOffset, vOffset: integer; BigBufInfo: InfoPtr; aLine: LineType; begin BigBufInfo := info; info := StackInfo; SetSlice(i); info := BigBufInfo; SetForegroundColor(BlackIndex); SetBackgroundColor(WhiteIndex); SelectAll(false); DoOperation(EraseOp); {write image one line at a time to center of larger buffer} hOffset := (bigpicwidth - picwidth) div 2; vOffset := (bigpicheight - picheight) div 2; for vloc := 0 to picheight - 1 do begin info := StackInfo; GetLine(0, vloc, picwidth, aLine); info := BigBufInfo; PutLine(hOffset, vloc + vOffset, picwidth, aLine); end; UpdatePicWindow; end; {RegisterCenterInBigBuffer} {******************************************************************************} {* After registration is complete, move the centered image back to its original window size. *} {******************************************************************************} procedure TranslateBackToStack (i, xdelta, ydelta, picwidth, picheight, bigpicwidth, bigpicheight: longint; StackInfo: InfoPtr); var vloc, hoffset, voffset: integer; offset: longint; BigBufInfo: InfoPtr; aLine: LineType; begin BigBufInfo := info; info := StackInfo; SetSlice(i); hOffset := (bigpicwidth - picwidth) div 2 - xdelta; vOffset := (bigpicheight - picheight) div 2 + ydelta; for vloc := 0 to picheight - 1 do begin info := BigBufInfo; GetLine(hOffset, vloc + vOffset, picwidth, aLine); info := StackInfo; PutLine(0, vloc, picwidth, aLine); end; UpdatePicWindow; info := BigBufInfo; end; {RegisterBackToSmallWindow} {******************************************************************************} {* Find the angle which the current slice must be rotated in order to place it in register with the reference *} {* slice. For corresponding pairs of fiducial points in the current and reference slices, use simple *} {* trigonometry to find the angle between lines passing through each pair of points. Take an everage of the *} {* angles found from each set of corresponding pairs of points to find the rotation angle. *} {******************************************************************************} function RegisterFindAngle (var fiducials: FidArray; Cur, Ref: integer): real; var j, k, n: integer; angle, anglecur, angleref: real; tancur, tanref: real; sumangle: real; begin {find average angle between current fiducial segments and reference fiducial segments} sumangle := 0; n := 0; for j := 1 to MaxFids - 1 do for k := j + 1 to MaxFids do if (j <> k) and (fiducials[Cur, j].x < biggestFid) and (fiducials[Cur, j].y < biggestFid) and (fiducials[Cur, k].x < biggestFid) and (fiducials[Cur, k].y < biggestFid) and (fiducials[Ref, j].x < biggestFid) and (fiducials[Ref, j].y < biggestFid) and (fiducials[Ref, k].x < biggestFid) and (fiducials[Ref, k].y < biggestFid) then begin tanref := (fiducials[Ref, k].y - fiducials[Ref, j].y) / (fiducials[Ref, k].x - fiducials[Ref, j].x); if ((tanref > 0) and (fiducials[Ref, k].y - fiducials[Ref, j].y < 0)) or ((tanref < 0) and (fiducials[Ref, k].y - fiducials[Ref, j].y > 0)) then angleref := arctan(tanref) + pi else angleref := arctan(tanref); tancur := (fiducials[Cur, k].y - fiducials[Cur, j].y) / (fiducials[Cur, k].x - fiducials[Cur, j].x); if ((tancur > 0) and (fiducials[Cur, k].y - fiducials[Cur, j].y < 0)) or ((tancur < 0) and (fiducials[Cur, k].y - fiducials[Cur, j].y > 0)) then anglecur := arctan(tancur) + pi else anglecur := arctan(tancur); angle := anglecur - angleref; if (angle > pi) then angle := angle - 2 * pi; if (angle <= -pi) then angle := angle + 2 * pi; sumangle := sumangle + angle; n := n + 1; end; {then} if (n > 0) then RegisterFindAngle := sumangle / n else begin PutMessage('Insufficient fiducial data to calculate registration rotation.'); RegisterFindAngle := 10000; end; {else} end; {function RegisterFindAngle} {*************************************} {* Rotates the slice using ScaleAndRotate routine. *} {*************************************} procedure RegisterRotate (AngleInRadians: real); begin rsHScale := 1.0; rsVScale := 1.0; rsAngle := (AngleInRadians / pi) * 180.0; if info^.LutMode = ColorLut then rsMethod := NearestNeighbor else rsMethod := Bilinear; rsCreateNewWindow := false; macro := true; {So ScaleAndRotate won't display its dialog box.} ScaleAndRotate; Macro := false; end; {******************************************************************************} {* Find the distances in x and y which the current slice must be translated in order to place it in register *} {* with the reference slice. *} {******************************************************************************} procedure FindTranslation (var xdelta, ydelta: integer; var fiducials: FidArray; i, RefPic: integer; xscale, yscale, angle: real; xc, yc: RegisterRealArray; xcenterStage, ycenterStage: real); var xcur, ycur: real; xdeltaindex: real; {used to detect non-linear mapping between coordinate systems of current} ydeltaindex: real; { and reference slices; the closer to zero, the more linear the mapping} xdeltamin, ydeltamin: real; {minimize indices to find best fiducials for translation calculations} j: integer; begin xdeltamin := biggestFid; ydeltamin := biggestFid; for j := 1 to MaxFids do if (fiducials[i, j].x < biggestFid) and (fiducials[i, j].y < biggestFid) then begin xcur := fiducials[i, j].x - xc[i]; ycur := fiducials[i, j].y - yc[i]; {rotate original fiducials about screen center} {this changes values of first two parameters on return} RotateAboutXY(xcur, ycur, -angle, xcenterStage, ycenterStage); {calculate translation offsets} xdeltaindex := abs(fiducials[i, j].x - xc[i]) + abs(fiducials[RefPic, j].x - xc[RefPic]); if (xdeltaindex < xdeltamin) then begin {try to minimize effect of warped tissue} xdelta := round(xscale * (fiducials[RefPic, j].x - xc[RefPic] - xcur)); xdeltamin := xdeltaindex; end; {then} ydeltaindex := abs(fiducials[i, j].y - yc[i]) + abs(fiducials[RefPic, j].y - yc[RefPic]); if (ydeltaindex < ydeltamin) then begin {try to minimize effect of warped tissue} ydelta := round(yscale * (fiducials[RefPic, j].y - yc[RefPic] - ycur)); ydeltamin := ydeltaindex; end; {then} end; {then} end; {procedure RegisterFindTranslation} {******************************************************************************} {* This procedure allows the user to determine, via radio buttons in a dialog box, whether fiducial data *} {* will be read from the screen or from a file. The dialog box also contains details about how to select fiducial *} {* points on the screen. *} {******************************************************************************} function RegisterOptions: boolean; var mylog: Dialogptr; {pointer to dialog box} i, item, alldone: integer; SaveFiducialMethod: FiducialMethodType; SaveConfirmFidClicks: boolean; begin RegisterOptions := FALSE; InitCursor; SaveConfirmFidClicks := ConfirmFidClicks; SaveFiducialMethod := FiducialMethod; mylog := GetNewDialog(RegisterImagesID, nil, pointer(-1)); OutlineButton(MyLog, ok, 16); SetDialogItem(MyLog, FiducialsOnScreenID, ord(FiducialMethod = OnScreen)); SetDialogItem(MyLog, FiducialsFromFileID, ord(FiducialMethod = FromFile)); SetDialogItem(MyLog, ConfirmFidClicksID, ord(ConfirmFidClicks)); alldone := 0; repeat {if we don't do it this way, ModalDialog throws us into code checking after each keystroke} repeat {meaning you can't type in a 2 digit number} ModalDialog(nil, item); if item = ConfirmFidClicksID then begin ConfirmFidClicks := not ConfirmFidClicks; SetDialogItem(MyLog, ConfirmFidClicksID, ord(ConfirmFidClicks)); end; if (item = FiducialsOnScreenID) or (item = FiducialsFromFileID) then begin case item of FiducialsOnScreenID: FiducialMethod := OnScreen; FiducialsFromFileID: FiducialMethod := FromFile; end; {case} SetDialogItem(MyLog, FiducialsOnScreenID, ord(FiducialMethod = OnScreen)); SetDialogItem(MyLog, FiducialsFromFileID, ord(FiducialMethod = FromFile)); end; until (item = ok) or (item = cancel); alldone := 1; until (alldone = 1); DisposDialog(mylog); if item = cancel then begin {if Cancel, keep the saved values} FiducialMethod := SaveFiducialMethod; ConfirmFidClicks := SaveConfirmFidClicks; Exit(RegisterOptions); end; RegisterOptions := TRUE; end; {******************************************************************************} {* Place a set of slices in register with a reference slice using fiducial marks. All slices in the stack *} {* are placed in register with the current slice using fiducial data gathered either from a text file or *} {* from the user's mouse clicks on the screen. *} {******************************************************************************} procedure DoRegister; var nImages: integer; {total number of open slices to register} RefImage: integer; {the index of the reference slice} bigpicwidth, bigpicheight: longint; {width,height of big buffer used for rotation and translation} picwidth, picheight: integer; {width,height of slices to register} xcenter, ycenter: integer; {coordinates of center of big, temp window} xdelta, ydelta: integer; {translation offsets} xcscreen, ycscreen: integer; {image center on the screen} xscreen1, yscreen1, xscreen2, yscreen2: integer;{two points in screen coordinates} xstage1, ystage1, xstage2, ystage2: real; {same two points in stage coordinates} xscale, yscale: real; {used for mapping stage to screen coords} xc, yc: RegisterRealArray; {array of image centers in stage coords} fiducials: FidArray; {array of fiducial point data for all slices} xcenterStage, ycenterStage: real; {used in translation calculation} angle: real; {mean angle between ref and cur fid segments} i, ignore: integer; {loop indices and temp var} TimeStr, seconds: str255; StartTicks, TicksForOneRegistration, TicksToGo: LongInt; StackInfo: InfoPtr; SlicesDone: integer; begin if not (RegisterOptions) then exit(DoRegister); with Info^ do begin picwidth := PixelsPerLine; picheight := nLines; RefImage := StackInfo^.CurrentSlice; nImages := StackInfo^.nSlices; end; if nImages > MaxRegSlices then begin PutMessage(concat('Unable to register more than ', long2str(MaxRegSlices), ' slices.')); exit(DoRegister); end; StackInfo := info; bigpicwidth := 2 * (round(1.414 * picwidth) div 2); {allow for image rotation without losing corners} bigpicheight := 2 * (round(1.414 * picheight) div 2); {odd window dims mysteriously don't work} if (bigpicwidth * bigpicheight) > UndoBufSize then begin PutMessage(concat('To register this stack, the size of the Undo buffer must be increased to at least ', long2str(bigpicwidth * bigpicheight div 1024), 'K.')); exit(DoRegister); end; xcenter := bigpicwidth div 2; {find center} ycenter := bigpicheight div 2; {open fiducial data file} {read fiducial marks and image centers into arrays; RegPic is image reference} {get screen to stage coordinate mapping} case FiducialMethod of OnScreen: if not GetFiducialDataFromScreen(xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2, xstage1, ystage1, xstage2, ystage2, fiducials, xc, yc) then exit(DoRegister); FromFile: if not GetFiducialDataFromFile(xcscreen, ycscreen, xscreen1, yscreen1, xscreen2, yscreen2, xstage1, ystage1, xstage2, ystage2, fiducials, xc, yc) then exit(DoRegister) end; {case} xscale := (xscreen2 - xscreen1) / (xstage2 - xstage1); yscale := (yscreen2 - yscreen1) / (ystage2 - ystage1); xcscreen := xcscreen + (bigpicwidth - picwidth) div 2; {adjust for bigger window} ycscreen := ycscreen + (bigpicheight - picheight) div 2; xcenterStage := (xcenter - xcscreen) / xscale; ycenterStage := (ycenter - ycscreen) / yscale; UpdatePicWindow; ShowWatch; i := 1; if not NewPicWindow('Temp', bigpicwidth, bigpicheight) then exit(DoRegister); ShowMessage(CmdPeriodToStop); SlicesDone := 1; {Don't need to process reference slice} while (i <= nImages) and not CommandPeriod do begin if i = RefImage then begin i := i + 1; if i > nImages then leave; end; StartTicks := TickCount; with info^ do SetWTitle(wptr, concat('Temp (', long2str(i), '/', long2str(nImages), ')')); {rotate image then translate to complete registration} angle := RegisterFindAngle(fiducials, i, RefImage); if (angle > 9999) then leave; CenterInBigBuffer(i, picwidth, picheight, bigpicwidth, bigpicheight, StackInfo); if (abs(angle) > 0.0001) then RegisterRotate(angle); if CommandPeriod then leave; FindTranslation(xdelta, ydelta, fiducials, i, RefImage, xscale, yscale, angle, xc, yc, xcenterStage, ycenterStage); TranslateBackToStack(i, xdelta, ydelta, picwidth, picheight, bigpicwidth, bigpicheight, StackInfo); SlicesDone := SlicesDone + 1; TicksForOneRegistration := TickCount - StartTicks; TicksToGo := (nImages - SlicesDone) * TicksForOneRegistration; NumToString((TicksToGo div 60) mod 60, seconds); if length(seconds) = 1 then seconds := concat('0', seconds); timestr := concat(long2str(TicksToGo div 3600), ':', seconds); ShowMessage(concat(CmdPeriodToStop, cr, 'time: ', timestr)); i := i + 1; end; {while i} Info^.changes := false; ignore := CloseAWindow(info^.wptr); info := StackInfo; if CommandPeriod then ShowMessage('Registration cancelled.') else begin SetSlice(RefImage); {select registered slice as current slice} UpdatePicWindow; info^.changes := true; ShowMessage('Registration successful.'); end; {else} end; end.